/*
 Developer: John Archer (john.archer.jpa@gmail.com)
 
 This file is part of VTBuilder.

 VTBuilder is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License.

 VTBuilder is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with VTBuilder.  If not, see <http://www.gnu.org/licenses/>.
 */
package gui.processor;

import core.para.Para;
import core.processor.Read;
import core.processor.Work;
import core.rb.*;
import core.sb.*;
import core.sb.s.Seq;
import core.tasks.DeNovo;
import core.tasks.Match;
import core.tasks.PairCheck;
import core.tasks.RPart;
import core.tasks.SPart;
import core.tasks.SPart_T;
import core.tasks.TScripts;
import gui.VTL1;
import gui.utils.ProgBar;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

public class Processor {

    public static int TASK_RUN_ASSEMBLY = 0;
    private VTL1 vtl1 = null;

    public Processor(VTL1 caller) {
        this.vtl1 = caller;
    }
    private int task = -1;

    public void setTask(int t) {
        task = t;
    }
    private static boolean B_LOCK = false;

    public void runTask() {
        if (B_LOCK == false) {
            B_LOCK = true;
            try {
                if (task == TASK_RUN_ASSEMBLY) {
                    getTScripts();
                }
            } catch (Exception e) {
                B_LOCK = false;
                idPrgA(ProgBar.END);
                dPrgA(ProgBar.END, 0);
                e.printStackTrace();
            }
            B_LOCK = false;
        }
    }

    public void loadCard(int which){
        if (Para.GUI) {
            vtl1.loadCard(which);
        }
    }
    
    public void renderFrontEnd(int n) {
        if (Para.GUI) {
            vtl1.renderFrontEnd(n);
        }
    }

    public void tickMSG(String msg) {
        if (Para.GUI) {
            vtl1.tickMSG(msg);
        } else {
            System.out.println(" " + msg);
        }
    }

    public void idPrgA(int w) {
        if (Para.GUI) {
            vtl1.idPrgA(w);
        }
    }

    public void dPrgA(int w, int v) {
        if (Para.GUI) {
            vtl1.dPrgA(w, v);
        }
    }

    //public static String logText = "";
    public static SB tScripts = null;
    public static int LONGEST_TRANSCRIPT;

    public void getTScripts() throws Exception {
        renderFrontEnd(VTL1.FLOWCHART);
        tScripts = null;
        String logText = "";
        logText += "Start loading reads: " + new java.util.Date() + "\n";
        tickMSG("Loading reads");
        renderFrontEnd(10);//loading reads
        idPrgA(ProgBar.START);
        ArrayList<String> readsP1 = loadAsString(Para.PATH_TO_FOWARD_READS, Para.PATH_TO_REVERSE_READS);
        idPrgA(ProgBar.END);
        logText += "\tMin. read len.: " + Para.MNRL + "\n";
        logText += "\tNo. of reads: " + readsP1.size() + "\n";
        logText += "End loading reads: " + new java.util.Date() + "\n";
        
        FileWriter logFile = new FileWriter(Para.PATH_TO_LOGFILE, false);
        logFile.write(logText);
        logFile.close();
        
        ArrayList<String> refs = phaseI(new ArrayList<String>(readsP1));
        phaseII(refs, readsP1);
        if (!Read.read) {
            (new Work(new Read())).execute();
        }
        if (tScripts.seqs.size() > 0) {
            if (!Para.PATH_TO_REVERSE_READS.equals("")) {
                phaseIII(readsP1, loadRev(Para.FILTERED_REV_FILE));
            }
            if (tScripts.seqs.size() > 0) {
                LONGEST_TRANSCRIPT = phaseIV(readsP1);
                tickMSG("Saving transcripts");
                renderFrontEnd(9);//loading reads
                tScripts.save(Para.PATH_TO_OUTPUT);
                Thread.sleep(3000);
                loadCard(VTL1.TRANSCRIPTS);
                renderFrontEnd(0);//reset to start card
            } else {
                tickMSG("No complete transcripts found");
                tScripts = null;
            }
        } else {
            tickMSG("No complete transcripts found");
            tScripts = null;
        }
        readsP1 = null;
        refs = null;
        System.gc();
    }
    
    static int LOGFILEWIDTH = 20;
    private ArrayList<String> phaseI(ArrayList<String> reads) throws Exception {
        tickMSG("Partitioning data");
        renderFrontEnd(1); //partitioning
        
        String logText = "\nStart partitioning: " + new java.util.Date() + "\n";
        ArrayList<IB> part = new RPart().part(reads, 3, this);
        reads.clear();reads = null;
        logText += "\tNo. of partitions: " + part.size() + "\n";
        logText += "\tPartition sizes:\n";
        for (int x = 0; x < part.size(); x++) {
            logText += "\t" + part.get(x).reads.size();
            if (x % LOGFILEWIDTH == 0 && x < 0) {
                logText += "\n";
            }
        }
        logText += "\nEnd partitioning: " + new java.util.Date() + "\n";

        FileWriter logFile = new FileWriter(Para.PATH_TO_LOGFILE, true);
        logFile.write(logText);
        logFile.close();
        
        logText = "\nStart de novo to produce references: " + new java.util.Date() + "\n";
        logText += "\tMin. len. (specified by user): " + Para.MNTLEN + "\n";       
        
        tickMSG("De Novo assembly of partitions");
        renderFrontEnd(2); // de novo  
        
        //denovo partitions
        ArrayList<String> seedsA = new DeNovo(part, true).deNovo(this);
        part.clear();part=null;
        SB sb_to_grp = new SB();
        
        //try it by removing this but keeping everything else the same
        //v4.1 etc later
        for (String r : seedsA) {
            if ((r.length()) > Para.MNTLEN) {
                sb_to_grp.seqs.add(new Seq(r));
            }
        }
        sb_to_grp.sort(false);      
        ArrayList<SB> grps = new SPart().sPart(sb_to_grp.seqs, this, Para.KTDN);
        sb_to_grp = null;
        
        ArrayList<String> refsA = new ArrayList<String>();
        for (int x = 0; x < grps.size(); x++) {
            refsA.add(grps.get(x).longest().dna);
        }
                        
        tickMSG("Finalizing reference sequences");
        renderFrontEnd(3); // grouping guides
        
        ArrayList<IB> bns = new ArrayList();
        bns.add(new IB(refsA));
        //at some point allow the user to add a reference transcriptome before this step
        ArrayList <String> seedsB = new DeNovo(bns, false).deNovo(this);
        
        sb_to_grp = new SB();
        for (String r : seedsB) {
            if ((r.length()) > Para.MNTLEN) {
                sb_to_grp.seqs.add(new Seq(r));
            }
        }
        sb_to_grp.sort(false);   
        grps = new SPart().sPart(sb_to_grp.seqs, this, Para.KTDN);
        sb_to_grp = null;
        ArrayList<String> refs = new ArrayList<String>();
        for (int x = 0; x < grps.size(); x++) {
            refs.add(grps.get(x).longest().dna);
        }
        
        logText += "\tNo of refs: " + refs.size() + "\n";
        logText += "\tRef. sizes:\n";
        for (int x = 0; x < refs.size(); x++) {
            logText += "\t" + refs.get(x).length();
            if (x % LOGFILEWIDTH == 0 && x < 0) {
                logText += "\n";
            }
        }
        
        logText += "\nEnd de novo to produce references: " + new java.util.Date() + "\n";

        logFile = new FileWriter(Para.PATH_TO_LOGFILE, true);
        logFile.write(logText);
        logFile.close(); 
        
        return refs;
    }

    private void phaseII(ArrayList<String> refs, ArrayList<String> phaseReads) throws Exception {
        tickMSG("Mapping all reads to reference sequences");
        renderFrontEnd(4); // mapping
        String logText = "\nStart mapping: " + new java.util.Date() + "\n";
        String bgRef = Para.SEPERATOR;
        for (int r = 0; r < refs.size(); r++) {
            bgRef += refs.get(r) + Para.SEPERATOR;
        }
        MB mb = new Match().mb(phaseReads, bgRef.length(), hash(bgRef), this);
        logText += "End mapping: " + new java.util.Date() + "\n";
        
        FileWriter logFile = new FileWriter(Para.PATH_TO_LOGFILE, true);
        logFile.write(logText);
        logFile.close();
        
        tickMSG("Constructing transcript sequences");
        renderFrontEnd(5); // constructing transcripts
        
        logText = "/nStart constructing transcripts: " + new java.util.Date() + "\n";
        tScripts = new TScripts().tscripts(mb, refs, this);
        mb = null;
        if (tScripts.seqs.size() > 1) {
            tickMSG("Finalizing transcript sequences");
            renderFrontEnd(6); // grouping transcripts
            tScripts.sort(false);
            ArrayList<SB> grps = new SPart_T().sPart(tScripts.seqs, this, Para.CLUST_SEQ_KT_TRANS);
            tScripts = new SB();
            for (SB sb : grps) {
                tScripts.seqs.add(sb.longest());
            }
        }
        logText += "\tMin. len. (specified by user): " + Para.MNTLEN + "\n";
        logText += "\tNo. of transcripts: " + tScripts.seqs.size() + "\n";
        logText += "\tTranscript. sizes:\n";
        for (int x = 0; x < tScripts.seqs.size(); x++) {
            logText += "\t" + tScripts.seqs.get(x).dna.length();
            if (x % LOGFILEWIDTH == 0 && x < 0) {
                logText += "\n";
            }
        }
        logText += "\nEnd constructing transcripts: " + new java.util.Date() + "\n";

        logFile = new FileWriter(Para.PATH_TO_LOGFILE, true);
        logFile.write(logText);
        logFile.close();
    }

    private void phaseIII(ArrayList<String> fReads, ArrayList<String> rReads) throws Exception {
        tickMSG("Applying paired end filtering");
        renderFrontEnd(7); // applying pairs
        String logText = "\nStart paired end filtering: " + new java.util.Date() + "\n";
        int pegStartEnds[][] = new int[2][tScripts.seqs.size()];
        String bgPegRef = Para.SEPERATOR;
        for (int r = 0; r < tScripts.seqs.size(); r++) {
            pegStartEnds[0][r] = bgPegRef.length();
            bgPegRef += tScripts.seqs.get(r).dna;
            pegStartEnds[1][r] = bgPegRef.length();
            bgPegRef += Para.SEPERATOR;
        }
        
        boolean[] valid = new PairCheck().pegPairCheckNew(fReads, rReads, bgPegRef, hash(bgPegRef), pegStartEnds, this);
        SB tScripts_new = new SB();
        for (int x = 0; x < valid.length; x++) {
            if (valid[x]) {
                tScripts_new.seqs.add(tScripts.seqs.get(x));
            }
        }
        logText += "\tNo. of transcripts after filtering: " + tScripts_new.seqs.size() + "\n";
        logText += "End paired end filtering: " + new java.util.Date() + "\n";
        
        FileWriter logFile = new FileWriter(Para.PATH_TO_LOGFILE, true);
        logFile.write(logText);
        logFile.close();
        
        tScripts = null;
        tScripts = tScripts_new;
    }

    private int phaseIV(ArrayList<String> readsP1) throws Exception {
        renderFrontEnd(8); // calculating expression rates
        tickMSG("Calculating expression rates");

        String logText = "\nStart calculating expression rates: " + new java.util.Date() + "\n";
        String bgRef = Para.SEPERATOR;
        for (int r = 0; r < tScripts.seqs.size(); r++) {
            bgRef += tScripts.seqs.get(r).dna + Para.SEPERATOR;
        }
        MB mb = new Match().mb(readsP1, bgRef.length(), hash(bgRef), this);
        readsP1.clear();
        int start, end, prevEnd = 0;
        double[] cover = new double[tScripts.seqs.size()];
        double[] length = new double[tScripts.seqs.size()];
        for (int x = 0; x < tScripts.seqs.size(); x++) {
            start = prevEnd + Para.SEPERATOR.length();
            prevEnd = end = start + tScripts.seqs.get(x).dna.length();
            cover[x] = mb.subMB(start, end).mrl.size();
            length[x] = end - start;
        }
        mb = null;
        DecimalFormat df = new DecimalFormat("##.####");
        double tot = 0;
        int longest = 0;
        for (int t = 0; t < cover.length; t++) {
            tot += (cover[t] / length[t]);
            if (length[t] > longest) {
                longest = (int) length[t];
            }
        }
        
        //longest title no
        int maxLn = (tScripts.seqs.size()+"").length();
        String tRdy = "";
        for (int t = 0; t < tScripts.seqs.size(); t++) {
            tRdy = t+"";
            while(tRdy.length()<maxLn){tRdy = "0"+tRdy;}
            tScripts.seqs.get(t).title = "T" + tRdy + "_R_" + df.format(((cover[t] / length[t]) / tot) * 100) + "_L_" + tScripts.seqs.get(t).dna.length();
        }
        logText += "\tRates (without rounding) are:\n";
        for (int x = 0; x < tScripts.seqs.size(); x++) {
            logText += "\t" + ((cover[x] / length[x]) / tot);
            if (x % LOGFILEWIDTH == 0 && x < 0) {
                logText += "\n";
            }
        }
        logText += "\nEnd calculating expression rates: " + new java.util.Date() + "\n";

        FileWriter logFile = new FileWriter(Para.PATH_TO_LOGFILE, true);
        logFile.write(logText);
        logFile.close();
        
        return longest;
    }

    public ArrayList<String> loadAsString(String fpath, String rpath) throws Exception {
        ArrayList<String> reads = new ArrayList<String>();
        BufferedReader fd = new BufferedReader(new FileReader(fpath));
        int count = 0;
        String fdna = "";
        if (rpath.equals("")) {
            while (fd.readLine() != null && count < Para.MR) {
                fdna = fd.readLine();
                fd.readLine();
                fd.readLine();
                if (fdna.length() >= Para.MNRL) {
                    reads.add(fdna);
                    count++;
                }
            }
            fd.close();
        } else {
            BufferedReader rv = new BufferedReader(new FileReader(rpath));
            String rdna = "";
            PrintWriter outFile = new PrintWriter(new FileWriter(Para.FILTERED_REV_FILE), false);
            while (fd.readLine() != null && count++ < Para.MR) {
                fdna = fd.readLine();
                fd.readLine();
                fd.readLine();
                rv.readLine();
                rdna = rv.readLine();
                //System.out.println(rdna.length());
                rdna = getMatchingStrand(new StringBuffer(rdna).reverse().toString());
                rv.readLine();
                rv.readLine();
                if (
                    fdna.length() >= Para.MNRL && 
                    rdna.length() >= Para.MNRL) {
                        reads.add(fdna);
                        outFile.println(rdna.substring(0, Para.MNRL));
                }
            }
            fd.close();
            rv.close();
            outFile.close();
        }
        return reads;
    }

    public ArrayList<String> loadRev(String rpathFiltered) throws Exception {
        ArrayList<String> reads = new ArrayList<String>();
        BufferedReader fd = new BufferedReader(new FileReader(rpathFiltered));
        String line = "";
        while ((line = fd.readLine()) != null) {
            reads.add(line);
        }
        fd.close();
        return reads;
    }

    public static String getMatchingStrand(String strand) {
        String str = "";
        for (int x = 0; x < strand.length(); x++) {
            if (strand.charAt(x) == 'A') {
                str += "T";
            } else if (strand.charAt(x) == 'T') {
                str += "A";
            } else if (strand.charAt(x) == 'G') {
                str += "C";
            } else if (strand.charAt(x) == 'C') {
                str += "G";
            } else {
                str += strand.charAt(x);
            }
        }
        return str;
    }

    public static HashMap<String, ArrayList<Integer>> hash(String dna) {
        HashMap<String, ArrayList<Integer>> hash = new HashMap<String, ArrayList<Integer>>();
        for (int loc = 0; loc < (dna.length() - 10); loc++) {
            if (hash.containsKey(dna.substring(loc, loc + 10))) {
                hash.get(dna.substring(loc, loc + 10)).add(loc);
            } else {
                hash.put(dna.substring(loc, loc + 10), new ArrayList<Integer>(Arrays.asList(loc)));
            }
        }
        return hash;
    }
}
